package com.szwistar.common.comm;

import com.szwistar.common.datastruct.Utils;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import okhttp3.ConnectionPool;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.Credentials;
import okhttp3.FormBody;
import okhttp3.FormBody.Builder;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;

/**
 * http 工具类
 */
public class HttpUtils {
	public static class HttpSession {
		static final int CONNECT_TIME_OUT = 10;
		static final int WRITE_TIME_OUT = 30;
		static final int READ_TIME_OUT = 10;

		// 用于捕获 sessionId 的 cookie 名称
		static final String[] defaultSessionIdCookieNames = new String[] {
			"sessionid", "jsessionid", "SecureSessionId"
		};

		// HttpClient
    	OkHttpClient client = null;
    	// SessionId: 名字为"SessionId"/"Jsessionid"的cookie的值
    	String sessionId = null;
		// 用于保存 Cookie 的Map
		HashMap<String, List<Cookie>> cookieStore = new HashMap<String, List<Cookie>>();

    	public OkHttpClient getClient() { return client; }
		public String getSessionId() { return sessionId; }
		public HashMap<String, List<Cookie>> getCookieStore() { return cookieStore; }

		/**
		 * 创建 HttpSession
		 * @param supportHttps 是否需要支持 https
		 */
		public HttpSession(boolean supportHttps) {
			this(supportHttps, defaultSessionIdCookieNames);
		}

		/**
		 * 创建 HttpSession
		 * @param supportHttps 是否需要支持 https
		 * @param sessionIdCookieNames 用于捕获 sessionId 的 cookie 名称
		 */
		@SuppressWarnings("deprecation")
		public HttpSession(boolean supportHttps, final String[] sessionIdCookieNames) {
            // 如果没有设置用于捕获 sessionId 的 cookie 名称，则使用默认的
			final String[] allSessionIdCookieNames = Utils.isEmpty(sessionIdCookieNames) ? defaultSessionIdCookieNames : sessionIdCookieNames;

			OkHttpClient.Builder builder = new OkHttpClient.Builder();
			builder.connectionPool(new ConnectionPool(2, 10, TimeUnit.SECONDS));
			builder.connectTimeout(CONNECT_TIME_OUT, TimeUnit.SECONDS);
			builder.readTimeout(READ_TIME_OUT, TimeUnit.SECONDS);
			builder.writeTimeout(WRITE_TIME_OUT, TimeUnit.SECONDS);

			// Cookie 存储与设置
			builder.cookieJar(new CookieJar() {
    	        @Override
    	        public List<Cookie> loadForRequest(HttpUrl url) {
    	        	String server = url.host() + ":" + url.port();
    	            List<Cookie> cookies = cookieStore.get(server);
    	            return cookies != null ? cookies : new ArrayList<Cookie>();
    	        }

				@Override
				public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
    	        	String server = url.host() + ":" + url.port();
    	            cookieStore.put(server, cookies);

    	            // 检查是否有 sessionid
    	            for(Cookie cookie: cookies) {
    	            	for(String name: allSessionIdCookieNames) {
    	            		if(name.equalsIgnoreCase(cookie.name())) {
    	            			sessionId = cookie.value();
    	            		}
    	            	}
    	            }
				}
    	    });

			// For HTTPS
	        if(supportHttps) {
	        	// 不做验证
				//builder.sslSocketFactory(SSLContextGenerator.getSslContext().getSocketFactory());
            	// 直接信任所有主机
		        //builder.hostnameVerifier(new HostnameVerifier() {
		        //    @Override public boolean verify(String hostname, SSLSession session) { return true; }
		        //});

	        	// 不做验证
		        builder.sslSocketFactory(SSLUtils.createSSLSocketFactory());
            	// 直接信任所有主机
		        builder.hostnameVerifier(new SSLUtils.TrustAllHostnameVerifier());
	        }

    		client = builder.build();
    	}

		/* 可使用以下方式创建 SSL，但此代码在 安卓 系统上无法运行！
		 * {
			TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
			trustManagerFactory.init((KeyStore) null);
			TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
			if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
				throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
			}
			X509TrustManager trustManager = (X509TrustManager) trustManagers[0];

			SSLContext sslContext = SSLContext.getInstance("TLS");
			sslContext.init(null, new TrustManager[] { trustManager }, null);
			SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

			OkHttpClient client = new OkHttpClient.Builder()
				.sslSocketFactory(sslSocketFactory, trustManager)
				.build();
		}*/

		/**
		 * 发送 http 请求
		 * @param url 请求的 URL，如果需要 BASIC 验证，请在 url 中添加验证信息，如：http://username:password@abc.com/
		 * @param headers 请求头中的参数表，如果值不是 String，将会被转换为 String
		 * @param urlParams 请求 URL 中的参数表
		 * @param postBody 要传递的 BODY。如果 postBody == null，则使用 GET 方法；否则使用 POST 方法:
		 * 	<li>如果是 RequestBody 则直接添加为 BODY；
		 *	<li>如果是字符串(可以是空字符串"")，则默认为 "application/json" 格式 BODY；
		 *	<li>如果是Map，则转换化为 FormBody。
		 * @return HTTP 返回的 body 字符串
		 * @throws Exception
		 */
    	public Response request(String url, Map<String, Object> headers, Map<String, Object> urlParams, Object postBody) throws Exception {
            HttpUrl httpUrl = HttpUrl.parse(url);
    		Request.Builder breq = new Request.Builder();

    		// 如果有请求头参数，则添加参数
            if(Utils.isNotEmpty(headers)) {
                for (String key: headers.keySet()) {
                	breq.header(key, headers.get(key).toString());
                }
            }

            // 如果提供了用户名密码，则在请求头中添加 BASIC 验证
            if(Utils.isNotEmpty(httpUrl.username()) && Utils.isNotEmpty(httpUrl.password())) {
            	String credential = Credentials.basic(httpUrl.username(), httpUrl.password());
            	breq.header("Authorization", credential);
            }

    		// 如果有 URL 参数则添加参数
            if(Utils.isNotEmpty(urlParams)) {
        		HttpUrl.Builder burl = httpUrl.newBuilder();
                for (String key: urlParams.keySet()) {
                    burl.addQueryParameter(key, urlParams.get(key).toString());
                }
                httpUrl = burl.build();
            }

            // 设置 URL
            breq.url(httpUrl);

            // 如果 postBody != null 则使用 POST 方法，并添加它
            if(postBody != null) {
            	// 自动类型转换
            	if(postBody instanceof RequestBody) {
            		// do nothing
            	}
            	else if(postBody instanceof String) {
            		postBody = RequestBody.create(MediaType.get("application/json"), (String)postBody);
            	}
            	else if(postBody instanceof Map) {
            		Builder formBody = new FormBody.Builder();
            		@SuppressWarnings("rawtypes")
					Map params = (Map)postBody;

            		for(Object key: params.keySet()) {
            			formBody.add(key.toString(), params.get(key).toString());
            		}

            		postBody = formBody.build();
            	}

                breq.post((RequestBody)postBody);
            }

            // 执行阻塞式访问
            return client.newCall(breq.build()).execute();
        }

    	/**
    	 * 上传文件
    	 * @param url 请求的 URL。如果需要 BASIC 验证，请在 url 中添加验证信息，如：http://username:password@abc.com/
    	 * @param formDataParts 要填写的额外表单字段。如果没有，则填写 null
    	 * @param file 要上传的本地文件
    	 * @param targetName 目标文件名
		 * @return HTTP 返回的 body 字符串
    	 * @throws Exception
    	 */
        public Response upload(String url, Map<String, String> formDataParts, File file, String targetName) throws Exception {
        	if(!file.exists() || !file.isFile()) { return null; }

        	MultipartBody.Builder mpbb = new MultipartBody.Builder()
					.setType(MultipartBody.FORM)
					.addFormDataPart("file", targetName, RequestBody.create(MediaType.parse("application/octet-stream"), file));

        	// 填写附加表单字段值
        	if(formDataParts != null) {
        		for(String key: formDataParts.keySet()) {
        			mpbb.addFormDataPart(key, formDataParts.get(key));
        		}
        	}

			Request request = new Request.Builder()
					.header("Authorization", "Client-ID " + UUID.randomUUID())
					.url(url)
					.post(mpbb.build())
					.build();

			Response response = client.newCall(request).execute();
			if (!response.isSuccessful()) {
				Exception exception = new IOException("Unexpected code " + response);
				response.close();
				throw exception;
			}

			return response;
		}
	}

	/**
	 * 发送 http 请求
	 * @see HttpUtils.request
	 */
	public static String request(String url, Map<String, Object> urlParams, String postBody) throws Exception {
		HttpUrl httpUrl = HttpUrl.parse(url);
		ResponseBody body = new HttpSession(httpUrl.isHttps()).request(url, null, urlParams, postBody).body();
		String ret = (body == null) ? null : body.string();
		body.close();
		return ret;
	}

	/**
	 * 上传文件
	 * @see HttpUtils.upload
	 */
	public static String upload(String url, Map<String, String> formDataParts, File file, String targetName) throws Exception {
		HttpUrl httpUrl = HttpUrl.parse(url);
		ResponseBody body = new HttpSession(httpUrl.isHttps()).upload(url, formDataParts, file, targetName).body();
		String ret = (body == null) ? null : body.string();
		body.close();
		return ret;
	}


	// For test only
	@SuppressWarnings("unused")
	public static void main(String[] args) throws Exception {
    	Response resp = null;
    	String body = null;

    	body = HttpUtils.request("https://www.szwistar.com/", null, null);
       	System.out.println(body);

       	Map<String, String> params = new HashMap<String, String>();
       	params.put("app_name", "test.bin");
       	params.put("version", "0.0.0");
       	params.put("publisher", "York Zhang");
       	params.put("whats_new", "hello");
       	body = HttpUtils.upload("https://ehome.szwistar.com/uploads", params, new File("c:\\windows\\system32\\explorer.exe"), "xxx.bin");
       	System.out.println(body);
       	body = HttpUtils.upload("https://ehome.szwistar.com/ota_upload", params, new File("c:\\windows\\system32\\explorer.exe"), "xxx.bin");
       	System.out.println(body);
	}
}
